package org.iks.user.util;

import cn.hutool.core.codec.Base64;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.IESParameterSpec;

import javax.crypto.Cipher;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;


/**
 * Author kzou
 * Date 2024/4/30
 * Description: secp256k1 工具类
 * y**2=x**3+7
 *
 * @see <a href="https://www.jianshu.com/p/6f08bebc5aa6">参考算法文档</a>
 */
public class Secp256k1Util {
    private static final String ALGORITHM = "EC";
    private static final String SECP256R1 = "secp256r1";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    public static PublicKey ofECPublicKey(String hexPubKey) throws Exception {
        if (hexPubKey.length() == 128 + 2) {
            hexPubKey = hexPubKey.substring(2);
            var x = hexPubKey.substring(0, 64);
            var y = hexPubKey.substring(64);
            return ofECPublicKey(
                    new BigInteger(x, 16),
                    new BigInteger(y, 16)
            );
        }
        // TODO
        return null;
    }
    public static PublicKey ofECPublicKey(BigInteger x, BigInteger y) throws Exception {
        ECPoint pubPoint = new ECPoint(x, y);
        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
        parameters.init(new ECGenParameterSpec(SECP256R1));
        ECParameterSpec parameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
        ECPublicKeySpec pubSpec = new ECPublicKeySpec(pubPoint, parameterSpec);
        KeyFactory kf = KeyFactory.getInstance("EC");
        return kf.generatePublic(pubSpec);
    }
    public static PublicKey ofECPublicKey(byte[] encode) throws Exception {
        X509EncodedKeySpec pkcs8EncodedKeySpec = new X509EncodedKeySpec(encode);
        KeyFactory kf = KeyFactory.getInstance("EC");
        return kf.generatePublic(pkcs8EncodedKeySpec);
    }

    public static byte[] encrypt(String src, PublicKey pubKey) throws Exception {
        byte[] srcData = src.getBytes(StandardCharsets.UTF_8);
        IESParameterSpec params = new IESParameterSpec(
                null,
                null,
                128,
                128,
                null
        );
        Cipher encryptCipher = Cipher.getInstance("ECIES","BC");
        encryptCipher.init(Cipher.ENCRYPT_MODE, pubKey, params);
        return encryptCipher.doFinal(srcData);
    }

    private static byte[] decrypt(byte[] encBytes, PrivateKey priKey) throws Exception {
        Cipher decryptCipher = Cipher.getInstance("ECIES", "BC");
        IESParameterSpec params = new IESParameterSpec(null, null, 128, 128, null);
        decryptCipher.init(Cipher.DECRYPT_MODE, priKey, params);
        return decryptCipher.doFinal(encBytes);
    }

    /**
     * 生成密钥对
     */
    public static KeyPair generateKeyPair() throws Exception {
        ECNamedCurveParameterSpec ecSpec  = ECNamedCurveTable.getParameterSpec(SECP256R1);
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, "BC");
        keyPairGenerator.initialize(ecSpec);
        return keyPairGenerator.generateKeyPair();
    }

    public static void main(String[] args) throws Exception {
        KeyPair keyPair = generateKeyPair();

        // 获取公钥和私钥
        BCECPublicKey publicKey = (BCECPublicKey)keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        String plainText = "Hello, World!";

        org.bouncycastle.math.ec.ECPoint q = publicKey.getQ();
        BigInteger _x = q.getRawXCoord().toBigInteger();
        BigInteger _y = q.getRawYCoord().toBigInteger();

        PublicKey pk = ofECPublicKey(_x, _y);
        // 加密
        byte[] encrypt = encrypt(plainText, pk);
        String encode = Base64.encode(encrypt);
        System.out.println("Encrypted Text: " + encode);

        byte[] decrypt = decrypt(encrypt, privateKey);
        System.out.println(new String(decrypt));
    }


}
